#include "SkyNet.h"

#ifdef USE_DSP
const int parallel_factor = 1;
#else
const int parallel_factor = 2;
#endif
const int unroll_factor = 32 / parallel_factor;

void PWCONV1X1_PL(
    const ADT IFM[32][43][83],
    BDT OFM[32][43][83],
    const WDT WBUF1x1[32][32],
    bool clear_enable
#ifdef USE_DSP
    ,
    bool ap_clk_div2
#endif
)
{
#pragma HLS ARRAY_PARTITION variable=OFM dim=1 complete
#pragma HLS ARRAY_PARTITION variable=IFM dim=1 complete
#pragma HLS ARRAY_PARTITION variable=WBUF1x1 dim=0 complete

    // IFM is a (43*83)*32 matrix, WBUF1x1 is a 32*32 matrix, OFM is a (43*83)*32 matrix
IFM_row_loop_i:
    for (int i = 0; i < 43; i++)
    {
    IFM_row_loop_j:
        for (int j = 0; j < 83; j++)
        {
#ifdef USE_DSP
#pragma HLS LOOP_FLATTEN
#pragma HLS PIPELINE II=1
            int ci = 0;
            ADT C1in_buf[16];
            ADT C2in_buf[16];
#else
        WBUF1x1_col_i_loop:
            for (int ci = 0; ci < parallel_factor; ci++)
            {
#pragma HLS LOOP_FLATTEN
#pragma HLS PIPELINE II=1
                ADT C1in_buf[16];
                ADT C2in_buf[16];
#endif
            WBUF1x1_col_j_loop:
            for (int cj = 0; cj < unroll_factor / 2; cj++)
            {
#pragma HLS UNROLL
#pragma HLS LATENCY min=1
                // Fully unrolled inner loop, which is the pe array
                const int c = ci * (unroll_factor / 2) + cj;

                ap_int<48> Accum;
                if (clear_enable)
                {
                    Accum = ap_int<48>(0);
                }
                else
                {
                    Accum.range(47, 40) = ap_int<8>(0);
                    Accum.range(39, 21) = ap_int<19>(OFM[2 * c + 0][i][j]);
                    Accum.range(20, 19) = ap_int<2>(0);
                    Accum.range(18, 0) = ap_int<19>(OFM[2 * c + 1][i][j]);
                }

            IFM_col_loop:
                for (int k = 0; k < 16; k++)
                {
#pragma HLS UNROLL
                    if (c == 0)
                    {
// #pragma HLS OCCURRENCE cycle=parallel_factor
                        C1in_buf[k] = IFM[2 * k + 0][i][j];
                        C2in_buf[k] = IFM[2 * k + 1][i][j];
                    }
                    const ADT C1in = C1in_buf[k];
                    const ADT C2in = C2in_buf[k];

                    const WDT A1in = WBUF1x1[2 * c + 0][2 * k + 0];
                    const WDT B1in = WBUF1x1[2 * c + 1][2 * k + 0];
                    const WDT A2in = WBUF1x1[2 * c + 0][2 * k + 1];
                    const WDT B2in = WBUF1x1[2 * c + 1][2 * k + 1];
#ifdef TWO_IN_ONE
#ifdef USE_DSP
                    ap_int<48> Result = __builtin_mac6x2_mac8x1(A1in, A2in, B1in, B2in, C1in, C2in, Accum, clear_enable && (k == 0), ap_clk_div2);
#else
                    ap_int<48> Result = ap_int<48>(((ap_int<27>(A1in) << 21) + ap_int<27>(B1in)) * C1in) + ap_int<48>(((ap_int<27>(A2in) << 21) + ap_int<27>(B2in)) * C2in) + ((clear_enable && (k == 0)) ? ap_int<48>(0) : Accum);
#endif

                    const ap_int<19> Result_1 = Result.range(39, 21) + (Result[18] & (!Accum[18]));
                    const ap_int<19> Result_2 = Result.range(18, 0);
#else
                    const ap_int<19> D1 = Accum.range(39,21);
                    const ap_int<19> D2 = Accum.range(20,19);
                    const ap_int<19> Result_1 = A1in * C1in + A2in * C2in + D1;
                    const ap_int<19> Result_2 = B1in * C1in + B2in * C2in + D2;
#endif
                    if (i > 0 && i < 42 && j > 0 && j < 82)
                    {
                        Accum.range(47, 40) = ap_int<8>(0);
                        Accum.range(39, 21) = Result_1;
                        Accum.range(20, 19) = ap_int<2>(0);
                        Accum.range(18, 0) = Result_2;
                    }
                }
                const ap_int<19> Result_1 = Accum.range(39, 21);
                const ap_int<19> Result_2 = Accum.range(18, 0);
                OFM[2 * c + 0][i][j] = Result_1 > smax ? BDT(smax) : Result_1 < smin ? BDT(smin) : BDT(Result_1);
                OFM[2 * c + 1][i][j] = Result_2 > smax ? BDT(smax) : Result_2 < smin ? BDT(smin) : BDT(Result_2);
#pragma HLS DEPENDENCE variable=OFM inter false
            }
#ifndef USE_DSP
        }
#endif
    }
}
}